package Core

import (
	"fmt"
	"os"
	"io/ioutil"
	"regexp"
	"strings"
	"reflect"
	"html/template"
	"log"
)

// declare HxBehavior struct
type HxBehavior struct {
	Behavior    string
	Request *HxRequest
	Response *HxResponse
	behaviorTpl []string
	theme       string
	cssFiles    map[string]string
	jsFiles     map[string]string
	viewData map[string]interface {}
	funcMap     template.FuncMap
	src         string
	behaviorResult map[string]string
}

// Check behavior is valid or not
func (this *HxBehavior) IsValid() error {
	tmp := strings.Split(this.Behavior, ":")
	if len(tmp) < 2 {
		panic("invalid behavior tag: " + this.Behavior)
	}
	this.behaviorTpl = []string{ThemeFile(tmp[0], tmp[1])}
	this.theme = Config.String("theme." + tmp[0])
	if _, e := os.Stat(this.behaviorTpl[0]); e != nil {
		return e
	}
	return nil
}

// Do behavior
func (this *HxBehavior) Do() {
	// read file bytes
	bytes, e := ioutil.ReadFile(this.behaviorTpl[0])
	if e != nil {
		panic(e)
	}
	// parse behaviors
	behaviors := this.parseBehaviors(bytes)
	if len(behaviors) > 0 {
		// if some behaviors, do them
		this.behaviorResult = map[string]string{}
		doResult := this.executeBehaviors(behaviors)
		if !doResult {
			panic("behaviors crash down")
		}
	}
	// render behavior-template files
	this.viewData["CSS"] = this.cssFiles
	this.viewData["JS"] = this.jsFiles
	this.viewData["THEME"] = this.theme
	this.src = RenderFiles(this.behaviorTpl, this.viewData, this.funcMap)
	// replace all results
	for b, str := range this.behaviorResult {
		this.src = strings.Replace(this.src, b, str, -1)
	}
	// set rendered content to response
	this.Response.Body = this.src
}

// parse behaviors by specific tags
func (this *HxBehavior) parseBehaviors(bytes []byte) []string {
	r, e := regexp.Compile("{@(.+?)}")
	if e != nil {
		panic(e)
	}
	matched := r.FindAll(bytes, -1)
	res := []string{}
	for _, m := range matched {
		res = append(res, string(m))
	}
	return res
}

// parse behavior tag to behavior name and calling args
func (this *HxBehavior) parseBehaviorTag(tag string) (string, []string) {
	tmp := strings.Replace(tag, "{@", "", -1)
	tmp = strings.Replace(tmp, ")}", "", -1)
	tmp = strings.Replace(tmp, "}", "", -1)
	tmpArray := strings.Split(tmp, "(")
	if len(tmpArray) < 2 {
		return tmpArray[0], []string{}
	}
	return tmpArray[0], strings.Split(tmpArray[1], ",")
}

// execute behaviors
func (this *HxBehavior) executeBehaviors(behaviors []string) bool {
	length := len(behaviors)
	// make buffer channel for waiting
	channel := make(chan bool, length)
	for _, behavior := range behaviors {
		// do behavior in goroutine
		go this.executeBehavior(behavior, channel)
	}
	// read channel and release lock
	for k := 0; k < length; k++ {
		// clear tag if goroutine crashed
		<-channel
	}
	return true
}

// execute one behavior
func (this *HxBehavior) executeBehavior(behavior string, ch chan bool) {
	funcName, args := this.parseBehaviorTag(behavior)
	f := funcTree[funcName]
	// check behavior is valid
	if !f.IsValid() {
		this.behaviorResult[behavior] = "<!-- invalid behavior '" + funcName + "' -->"
		ch<-false
		return
	}
	// check args are suited
	if len(args) != f.Type().NumIn() - 1 {
		this.behaviorResult[behavior] = "<!-- invalid behavior param '" + funcName + "' -->"
		ch<-false
		return
	}
	// recover panic in goroutine
	defer func() {
		e := recover()
		if e != nil {
			this.behaviorResult[behavior] = "<!-- panic behavior '" + funcName + "' -->"
			log.Println("behavior panic", behavior, e)
			Logger.Err("behavior panic", behavior, e)
			ch<-false
		}
	}()
	// assemble args, first arg must be this behavior struct
	fArgs := make([]reflect.Value, len(args) + 1)
	fArgs[0] = reflect.ValueOf(this)
	for k, v := range args {
		fArgs[k + 1] = reflect.ValueOf(v)
	}
	// call behavior
	result := f.Call(fArgs)
	// replace behavior tag to called result string
	this.behaviorResult[behavior] = result[0].String()
	ch<-true
}

// Create template object for executing behavior
func (this *HxBehavior) Template() *HxTemplate {
	t := &HxTemplate{}
	t.behavior = this
	t.viewData = make(map[string]interface {})
	t.funcs = make(template.FuncMap)
	return t
}

// Do behavior in a function
func DoBehavior(behavior string, request *HxRequest, response *HxResponse) *HxBehavior {
	b := &HxBehavior{}
	b.Behavior = behavior
	if e := b.IsValid(); e != nil {
		panic("invalid behavior '" + behavior + "' " + fmt.Sprint(e))
	}
	b.Request = request
	b.Response = response
	b.cssFiles = map[string]string{}
	b.jsFiles = map[string]string{}
	b.viewData = make(map[string]interface {})
	return b
}
